﻿namespace FlexPlatform.BusinessLogic.Controllers
{
    using System;
    using System.Data.Entity;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Collections.Generic;

    using FlexPlatform.BusinessLogic.Validation;
    using FlexPlatform.Common;
    using FlexPlatform.Common.Configuration;
    using FlexPlatform.GeneralUse;
    using System.ComponentModel;

    using FlexPlatform.Shared.Configuration;
    using FlexPlatform.Shared.DataModel;
    using FlexPlatform.Shared.Events;
    using FlexPlatform.Shared.Validation;
    using FlexPlatform.DataModel;
    
    /// <summary>
    /// Controls all the data changes inside a DataEntity.
    /// Validation results will be communicated through events to the EntityPresenter.
    /// The EntityController will be created by the EntityPresenter, on its creation.
    /// </summary>
    public class DataEntityController : IDataEntityController
    {
        #region Private Fields & Constructors

        private IDataEntity clsDataEntity;
        private readonly DbContext clsDataContext;
        private IValidator clsValidator;
        IEnumerable<IConfigurationElement> clsConfigurationElements;
        
        /// <summary>
        /// The collection of Property controllers.
        /// Each property has a property controller attached.
        /// </summary>
        private Dictionary<string, PropertyController> clsPropertyControllers;

        /// <summary>
        /// Creates a new instance of <see cref="DataEntityController"/>.
        /// The controller attaches automatically to the provided data entity.
        /// </summary>
        public DataEntityController(IDataEntity dataEntity)
        {
            // retrieve the configuration for this entity type
            clsConfigurationElements = SystemGlobal.Configuration.GetAllConfigurationElements(dataEntity.GetType());
            var validationRules = clsConfigurationElements.OfType<IValidationRuleConfiguration>();

            clsValidator = new Validator(validationRules.Select(x => x.ValidationRule));

            this.SetDataEntity(dataEntity);
        }

        #endregion

        #region Raised Events

        public event EventHandler<ValidationCompletedArgs> ValidationCompleted;

        public event EventHandler<PropertyValidationCompletedArgs> PropertyValidationCompleted;

        #endregion
                
        /// <summary>
        /// Gets the controlled data entity.
        /// </summary>
        public IDataEntity DataEntity
        {
            get
            {
                return clsDataEntity;
            }            
        }

        #region Private methods

        /// <summary>
        /// Sets the controlled data entity.
        /// </summary>
        /// <param name="dataEntity"></param>
        private void SetDataEntity(IDataEntity dataEntity)
        {
            // retrieve the DataContext from the Workspace of the provided DataEntity.
            clsDataEntity = dataEntity;

            // build-up the controllers for all the properties of DataEntity.
            BuildPropertyControllers();

            // Attach the event handlers for property updates
            clsDataEntity.BeforePropertyChange += OnBeforePropertyChange;
            clsDataEntity.PropertyChanged += OnPropertyChanged;
        }

        /// <summary>
        /// Creates a property controller for each public property in the DataEntity.
        /// </summary>
        private void BuildPropertyControllers()
        {
            Type dataEntityType = this.DataEntity.GetType();
            clsPropertyControllers = new Dictionary<string, PropertyController>();

            // for each property in controlled DataEntity, create a PropertyController object
            var publicPropeties = ReflectionHelper.GetPropertyInfos(dataEntityType);
            foreach (var dataPropertyExpression in publicPropeties)
            {
                var propertyController = new PropertyController(dataPropertyExpression.Name, clsConfigurationElements);
                clsPropertyControllers.Add(propertyController.PropertyName, propertyController);

                // Attach to events raised by the property controllers
                propertyController.ValidationCompleted += this.OnPropertyValidationCompleted;
            }
        }

        /// <summary>
        /// Gets the property controller allowing the property to be expressed as a lambda expression.
        /// </summary>
        /// <param name="propertyName"> The name of the property.</param>
        /// <returns> The associated property controller.</returns>
        private PropertyController GetPropertyController(string propertyName)
        {
            // The propery controllers are stored in the dictionary as: PropertyName - PropertyController.
            PropertyController foundProperty = null;
            this.clsPropertyControllers.TryGetValue(propertyName, out foundProperty);

            return foundProperty;
        }

        #endregion

        #region Event Handling

        /// <summary>
        /// Handles the DataEntity.BeforePropertyChange event.
        /// </summary>
        /// <param name="eventArgs">The extra data handled event provides.</param>
        /// <param name="sender">The sender of this event.</param>
        private void OnBeforePropertyChange(object sender, BeforeProperyChangeEventArgs eventArgs)
        {
            // identify the affected property
            PropertyController targetProperty = GetPropertyController(eventArgs.PropertyName);
            
            // Invoke the property controller's logic for handling this event.
            targetProperty.Process_OnBeforePropertyChange(eventArgs);
        }

        /// <summary>
        /// Processes the change within a property of the controlled DataEntity.
        /// </summary>
        /// <param name="sender"> The event sender.</param>
        /// <param name="eventArgs"> The event extra data.</param>
        private void OnPropertyChanged(Object sender, PropertyChangedEventArgs eventArgs)
        {
            
        }

        
        #region Events from Internal Components

        /// <summary>
        /// Handles the PropertyController.ValidationCompleted event.
        /// Forwards the event as PropertyValidationCompleted event.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event extra data.</param>
        private void OnPropertyValidationCompleted(Object sender, PropertyValidationCompletedArgs e)
        {
            if (PropertyValidationCompleted != null)
            {
                PropertyValidationCompleted(this, e);
            }
        }

        #endregion

        #endregion
    }
}
